/**
 * Copyright (C), 2023-2033
 */
package com.goaly.util;

import static cn.hutool.core.text.StrPool.AT;
import static com.goaly.consts.TestCodeConstant.ANNO_BEFORE_4;
import static com.goaly.consts.TestCodeConstant.ANNO_BEFORE_EACH_5;
import static com.goaly.consts.TestCodeConstant.ANNO_TEST_4;
import static com.goaly.consts.TestCodeConstant.ANNO_TEST_5;
import static com.goaly.consts.TestCodeConstant.BASE_PACKAGE_TEST;
import static com.goaly.consts.TestCodeConstant.BASE_PACKAGE_TEST_5;
import static com.goaly.consts.TestCodeConstant.IMPORT_TEST_4;
import static com.goaly.consts.TestCodeConstant.IMPORT_TEST_5;
import static com.goaly.consts.TestCodeConstant.JUNIT4_IMPORTS;
import static com.goaly.consts.TestCodeConstant.JUNIT5_IMPORTS;

import cn.hutool.core.lang.Opt;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.intellij.formatting.service.FormattingServiceUtil;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiImportList;
import com.intellij.psi.PsiImportStatement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.impl.light.LightFieldBuilder;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.util.PsiTreeUtil;
import groovy.lang.Tuple2;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.net.util.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * 虚拟文件工具类
 *
 * @author: lys
 * @date: 2023/6/16 12:03
 */
public class VirtualFileUtil {

    private static final Logger LOG = Logger.getInstance(VirtualFileUtil.class);

    /**
     * 创建虚拟文件
     *
     * @param baseDir     基础目录
     * @param fileName    文件名
     * @param fileContent 文件内容
     * @return 虚拟文件
     */
    public static Tuple2<VirtualFile, Boolean> createOrGetVirtualFile(VirtualFile baseDir, String fileName,
            String fileContent) {
        Application application = ApplicationManager.getApplication();
        Tuple2<VirtualFile, Boolean> tuple2 = application.<Tuple2<VirtualFile, Boolean>>runWriteAction(() -> {
            boolean isCreate = false;
            try {
                // 生成测试文件
                isCreate = VirtualFileUtil.createChildFile(baseDir, fileName, fileContent);
                return new Tuple2<>(baseDir.findFileByRelativePath(fileName), isCreate);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return new Tuple2<>(null, isCreate);
        });
        return tuple2;
    }


    /**
     * 创建子文件及相关目录，如果文件已存在则不创建 - 必须从Swing分派线程调用 Application.runWriteAction
     *
     * @param baseDir 基础目录
     * @param name    文件名
     * @param text    文件内容
     * @return 是否创建
     * @throws IOException
     */
    public static boolean createChildFile(@NotNull VirtualFile baseDir, @NotNull String name, @NotNull String text)
            throws IOException {
        boolean isCreate = false;
        String newDirectories = null;
        String fileName = name;
        VirtualFile dir = baseDir;
        if (name.contains(StrPool.SLASH)) {
            int pos = name.lastIndexOf(StrPool.SLASH);
            fileName = name.substring(pos + 1);
            newDirectories = name.substring(0, pos);
        }
        if (newDirectories != null) {
            dir = VfsUtil.createDirectoryIfMissing(baseDir, newDirectories);
        }
        if (dir != null) {
            VirtualFile virtualFile = dir.findChild(fileName);
            if (virtualFile == null) {
                virtualFile = dir.createChildData(baseDir, fileName);
                isCreate = true;
            }
            if (FileNmUtil.isImage(name)) {
                virtualFile.setBinaryContent(Base64.decodeBase64(text));
            } else {
                VfsUtil.saveText(virtualFile, text);
            }
        }
        return isCreate;
    }

    /**
     * 删除虚拟文件
     *
     * @param requestor   请求者
     * @param virtualFile 虚拟文件
     */
    public static void delete(VirtualFile requestor, VirtualFile virtualFile) {
        ApplicationManager.getApplication().runWriteAction(() -> {
            // 删除临时文件
            try {
                virtualFile.delete(requestor);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 合并Java代码
     *
     * @param project     项目
     * @param targetFile  目标文件
     * @param psiTempFile 临时文件
     */
    public static void mergeJavaCode(Project project, VirtualFile targetFile, PsiFile psiTempFile) {
        PsiClass tempClass = PsiTreeUtil.getChildOfType(psiTempFile, PsiClass.class);
        if (tempClass == null) {
            // 临时文件java类为空
            LOG.warn("临时文件java类为空");
            return;
        }
        PsiImportList tempPsiImportList = PsiTreeUtil.findChildOfType(psiTempFile, PsiImportList.class);
        PsiAnnotation[] tempAnnotations = tempClass.getAnnotations();
        PsiField[] tempFields = tempClass.getAllFields();
        PsiMethod[] tempMethods = tempClass.getMethods();

        PsiFile psiTargetFile = PsiManager.getInstance(project).findFile(targetFile);
        PsiClass targetClass = PsiTreeUtil.getChildOfType(psiTargetFile, PsiClass.class);

        PsiImportList targetPsiImportList = PsiTreeUtil.findChildOfType(psiTargetFile, PsiImportList.class);
        PsiAnnotation[] targetAnnotations = targetClass.getAnnotations();
        PsiField[] targetFields = targetClass.getFields();
        PsiMethod[] targetMethods = targetClass.getMethods();

        if (ArrayUtil.isNotEmpty(tempMethods)) {
            WriteCommandAction.runWriteCommandAction(project, () -> {
                try {
                    // 合并导入包
                    Tuple2<Boolean, Boolean> junitTuple2 = mergeImports(psiTargetFile, targetPsiImportList,
                            tempPsiImportList);
                    // 合并注解
                    mergeAnnotations(targetClass, targetAnnotations, tempAnnotations, junitTuple2);
                    // 合并字段
                    mergeFields(targetClass, targetFields, tempFields);
                    // 合并方法
                    mergeMethods(targetClass, targetMethods, tempMethods, junitTuple2);
                    // 刷新文件
                    targetFile.refresh(false, false);
                    // 格式化空格缩进
                    FormattingServiceUtil.formatElement(psiTargetFile, true);
                } finally {
                    PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
                    manager.commitDocument(manager.getDocument(psiTargetFile));
                    psiTempFile.clearCaches();
                }

            });
        }

    }

    /**
     * 根据完整类名生成Psi导入语句
     *
     * @param factory            Psi元素工厂
     * @param qualifiedClassName 完整类名
     * @return Psi导入语句
     */
    @NotNull
    public static PsiElement createImportStatement(PsiElementFactory factory, String qualifiedClassName) {
        PsiElement el;
        el = factory.createImportStatementOnDemand(qualifiedClassName);
        for (PsiElement elChild : el.getChildren()) {
            if (StrUtil.equals(elChild.getText(), StrPool.DOT)) {
                elChild.delete();
            } else if (StrUtil.equals(elChild.getText(), "*")) {
                elChild.delete();
            }
        }
        return el;
    }

    /**
     * 合并导入
     *
     * @param psiTargetFile       目标文件
     * @param targetPsiImportList 目标导入
     * @param sourcePsiImportList 来源导入
     * @return booleanTuple2 二元组，第一个元素表示是否有JUnit4相关包，第二个元素表示是否有JUnit5相关包
     */
    private static Tuple2<Boolean, Boolean> mergeImports(PsiFile psiTargetFile,
            PsiImportList targetPsiImportList,
            PsiImportList sourcePsiImportList) {
        Tuple2<Boolean, Boolean> booleanTuple2 = new Tuple2<>(false, false);
        if (sourcePsiImportList == null || !sourcePsiImportList.isValid()) {
            return booleanTuple2;
        }

        if (targetPsiImportList == null) {
            // 目标文件没有导入，直接添加
            psiTargetFile.addBefore(sourcePsiImportList, PsiTreeUtil.getChildOfType(psiTargetFile, PsiClass.class));
        } else if (!targetPsiImportList.isValid()) {
            // 目标文导入无效，直接替换
            targetPsiImportList.replace(sourcePsiImportList);
        } else {
            // 合并导入
            Map<String, PsiElement> targetImpStr2PsiEl = Arrays.stream(targetPsiImportList.getChildren())
                    .filter(PsiImportStatement.class::isInstance)
                    .collect(Collectors.toMap(el -> el.getText(), Function.identity(), (k1, k2) -> k1));

            AtomicBoolean hasJunit4 = new AtomicBoolean(false), hasJunit5 = new AtomicBoolean(false);
            for (String targetImpStr : targetImpStr2PsiEl.keySet()) {
                if (JUNIT4_IMPORTS.indexOf(targetImpStr) > -1) {
                    hasJunit4.set(true);
                    break;
                } else if (JUNIT5_IMPORTS.indexOf(targetImpStr) > -1) {
                    hasJunit5.set(true);
                    break;
                }
            }
            booleanTuple2 = new Tuple2<>(hasJunit4.get(), hasJunit5.get());
            PsiElementFactory factory = PsiElementFactory.SERVICE.getInstance(psiTargetFile.getProject());

            Arrays.stream(sourcePsiImportList.getChildren())
                    .filter(PsiImportStatement.class::isInstance)
                    .filter(el -> !targetImpStr2PsiEl.containsKey(el.getText()))
                    .forEach(el -> {
                        // junit4和junit4 @Test导入包兼容处理
                        el = compatibleAnnoTest(targetImpStr2PsiEl, hasJunit4.get(), hasJunit5.get(), factory, el);
                        if (el == null) {
                            return;
                        }

                        if (!targetImpStr2PsiEl.isEmpty()) {
                            // 添加换行
                            targetPsiImportList.add(CodeEditUtil.createLineFeed(targetPsiImportList.getManager()));
                        }
                        targetPsiImportList.add(el);
                    });

        }
        return booleanTuple2;
    }

    /**
     * 兼容junit4和junit5 @Test注解
     *
     * @param targetImpStr2PsiEl
     * @param hasJunit4
     * @param hasJunit5
     * @param factory
     * @param el
     * @return
     */
    @Nullable
    private static PsiElement compatibleAnnoTest(Map<String, PsiElement> targetImpStr2PsiEl, boolean hasJunit4,
            boolean hasJunit5, PsiElementFactory factory, PsiElement el) {

        if (el.getText().equals(IMPORT_TEST_4)) {
            // junit4 @Test
            if (hasJunit5) {
                // target已经导入junit5相关包
                if (targetImpStr2PsiEl.containsKey(IMPORT_TEST_5)) {
                    // 已导入junit5的Test注解，退出
                    return null;
                }
                // 没有导入junit5的Test注解，添加
                el = createImportStatement(factory, ANNO_TEST_5);
            }
        } else if (el.getText().equals(IMPORT_TEST_5)) {
            // junit5 @Test
            if (hasJunit4) {
                // target已经导入junit4相关包
                if (targetImpStr2PsiEl.containsKey(IMPORT_TEST_4)) {
                    // 已导入junit4的Test注解，退出
                    return null;
                }
                // 没有导入junit4的Test注解，添加
                el = createImportStatement(factory, ANNO_TEST_4);
            }
        } else if (el.getText().indexOf(BASE_PACKAGE_TEST_5) > -1) {
            // 当前包为junit5相关包
            if (hasJunit4) {
                // target已经导入junit4相关包，退出
                return null;
            }
        } else if (el.getText().indexOf(BASE_PACKAGE_TEST) > -1) {
            // 当前包为junit4相关包
            if (hasJunit5) {
                // target已经导入junit5相关包，退出
                return null;
            }
        }
        return el;
    }

    /**
     * 合并注解
     *
     * @param targetClass       目标类
     * @param targetAnnotations 目标注解
     * @param sourceAnnotations 源注解
     * @param junitTuple2       junit4和junit5注解标识
     */
    private static void mergeAnnotations(PsiClass targetClass, PsiAnnotation[] targetAnnotations,
            PsiAnnotation[] sourceAnnotations, Tuple2<Boolean, Boolean> junitTuple2) {

        boolean hasJunit4 = junitTuple2.getFirst(), hasJunit5 = junitTuple2.getSecond();
        Map<String, PsiAnnotation> name2TargetAnnotation = Arrays.stream(targetAnnotations)
                .collect(Collectors.toMap(anno -> StringUtil.getShortName(anno.getQualifiedName()),
                        Function.identity(), (k1, k2) -> k1));
        for (PsiAnnotation newAnno : sourceAnnotations) {
            if (checkIgnore4TestUnique(hasJunit4, hasJunit5, newAnno)) {
                continue;
            }
            if (ArrayUtil.isNotEmpty(targetAnnotations)) {
                // 有注解，替换

                PsiModifierList modifierList = targetClass.getModifierList();
                String annoShortName = StringUtil.getShortName(newAnno.getQualifiedName());
                PsiAnnotation targetAnnotation = name2TargetAnnotation.get(annoShortName);
                if (targetAnnotation != null) {
                    // 替换
                    targetAnnotation.replace(newAnno);
                    name2TargetAnnotation.put(annoShortName, newAnno);
                } else {
                    // 新增
                    modifierList.addBefore(newAnno, modifierList.getFirstChild());
                    name2TargetAnnotation.put(annoShortName, newAnno);
                }
            } else {
                // 没有注解，直接添加
                PsiModifierList modifierList = targetClass.getModifierList();
                modifierList.addBefore(newAnno, modifierList.getFirstChild());
            }
        }
    }

    /**
     * 检查是否忽略junit4或junit5注解
     *
     * @param hasJunit4  是否有junit4
     * @param hasJunit5  是否有junit5
     * @param annotation 注解
     * @return 是否忽略
     */
    private static boolean checkIgnore4TestUnique(boolean hasJunit4, boolean hasJunit5, PsiAnnotation annotation) {
        if (StrUtil.contains(annotation.getQualifiedName(), BASE_PACKAGE_TEST_5)) {
            // junit5相关注解
            if (hasJunit4) {
                // target已经导入junit4相关包，不处理junit5相关注解
                return true;
            }

        } else if (StrUtil.contains(annotation.getQualifiedName(), BASE_PACKAGE_TEST)) {
            // junit4相关注解
            if (hasJunit5) {
                // target已经导入junit5相关包，不处理junit4相关注解
                return true;
            }
        }
        return false;
    }

    /**
     * 合并字段
     *
     * @param targetClass  目标类
     * @param targetFields 目标字段
     * @param sourceFields 临时字段
     */
    private static void mergeFields(PsiClass targetClass, PsiField[] targetFields, PsiField[] sourceFields) {
        if (ArrayUtil.isNotEmpty(targetFields)) {
            // 有字段，替换
            Map<String, PsiField> name2TargetField = Arrays.stream(targetFields)
                    .collect(Collectors.toMap(PsiField::getName, Function.identity(), (k1, k2) -> k1));
            for (PsiField newField : sourceFields) {
                PsiField targetField = name2TargetField.get(newField.getName());
                if (LightFieldBuilder.class.isInstance(targetField) || LightFieldBuilder.class.isInstance(newField)) {
                    // 注解生成字段
                    continue;
                }
                if (targetField != null) {
                    // 替换
                    targetField.replace(newField);
                } else {
                    // 添加
                    targetClass.add(newField);
                }
            }
        } else {
            // 没有字段，添加
            for (PsiField newField : sourceFields) {
                if (LightFieldBuilder.class.isInstance(newField)) {
                    // 注解生成字段
                    continue;
                }
                // 添加非注解生成字段
                targetClass.add(newField);
            }
        }
    }

    /**
     * 合并方法
     *
     * @param targetClass   目标类
     * @param targetMethods 目标方法
     * @param sourceMethods 源方法
     * @param junitTuple2   junit4和junit5注解标识
     */
    private static void mergeMethods(PsiClass targetClass, PsiMethod[] targetMethods, PsiMethod[] sourceMethods,
            Tuple2<Boolean, Boolean> junitTuple2) {
        boolean hasJunit4 = junitTuple2.getFirst(), hasJunit5 = junitTuple2.getSecond();
        for (PsiMethod newMethod : sourceMethods) {

            boolean isTestMethod = false;
            PsiAnnotation[] newMethodAnnos = newMethod.getAnnotations();
            for (PsiAnnotation methodAnno : newMethodAnnos) {
                if (!isTestMethod) {
                    isTestMethod = "@Test".equals(methodAnno.getText());
                }
                if (checkIgnore4TestUnique(hasJunit4, hasJunit5, methodAnno)) {
                    if ("@Test".equals(methodAnno.getText())) {
                        // 替换不兼容的方法注解
                        String annoText = AT + ANNO_TEST_5;
                        if (hasJunit4) {
                            annoText = AT + ANNO_TEST_4;
                        }
                        methodAnno.replace(JavaPsiFacade.getElementFactory(targetClass.getProject())
                                .createAnnotationFromText(annoText, targetClass));
                    } else if (hasJunit4 && "@BeforeEach".equals(methodAnno.getText())) {
                        // 替换不兼容的方法注解
                        methodAnno.replace(JavaPsiFacade.getElementFactory(targetClass.getProject())
                                .createAnnotationFromText(AT + ANNO_BEFORE_4, targetClass));
                    } else if (hasJunit5 && "@Before".equals(methodAnno.getText())) {
                        // 替换不兼容的方法注解
                        methodAnno.replace(JavaPsiFacade.getElementFactory(targetClass.getProject())
                                .createAnnotationFromText(AT + ANNO_BEFORE_EACH_5, targetClass));
                    } else {
                        // 删除不兼容的方法注解
                        methodAnno.delete();
                    }
                }
            }
            // 经过以上注解处理后，重新获取方法注解
            newMethodAnnos = newMethod.getAnnotations();

            if (ArrayUtil.isNotEmpty(targetMethods)) {
                // 有方法，替换
                Map<String, PsiMethod> name2TargetMethod = Arrays.stream(targetMethods)
                        .collect(Collectors.toMap(PsiMethod::getName, Function.identity(), (k1, k2) -> k1));
                PsiMethod targetMethod = name2TargetMethod.get(newMethod.getName());
                if (targetMethod != null) {
                    // 合并或替换方法中的语句
                    if (isTestMethod) {
                        // 替换
                        targetMethod.replace(newMethod);
                    } else {
                        // 合并
                        Opt.ofNullable(newMethod.getBody()).map(PsiCodeBlock::getStatements).ifPresent(sts ->
                                Arrays.stream(sts).forEach(st -> targetMethod.getBody().add(st)));
                        for (PsiAnnotation newMethodAnno : newMethodAnnos) {
                            if (!newMethodAnno.isValid() || targetMethod.hasAnnotation(
                                    newMethodAnno.getQualifiedName())) {
                                continue;
                            }
                            // 添加注解
                            targetMethod.getModifierList().addBefore(newMethodAnno, targetMethod.getModifierList()
                                    .getFirstChild());
                        }
                    }
                } else {
                    // 插入
                    targetClass.add(newMethod);
                }
            } else {
                // 直接插入
                targetClass.add(newMethod);
            }
        }
    }

}
